#
# Copyright 2009, Jeffrey Palm.
#

require 'open-uri'
require 'rexml/document'
require 'cgi'
require 'util'

require 'itunes'
require 'server'
require 'config'
require 'track'
require 'common_interface'

# Holds functionality that's not specific to any device, but specific
# to running on a laptop
class Common < CommonInterface
  
  # Members
  attr_accessor                      :verbose
  attr_accessor                      :shared_key
  attr_accessor                      :itunes
  
  def initialize(base_url=nil)
    @base_url   = base_url ? base_url : BASE_URL
    @verbose    = false
    @shared_key = nil
    @itunes     = ITunes.new_instance
  end
  
  # ----------------------------------------------------------------------
  # Members
  # ----------------------------------------------------------------------

  # Need to override the assignment so that *every* time we assign to
  # it, it's written to disk
  def shared_key=(val)
    note 'Assigning the shared key: ' + val.to_s
    @shared_key = val
    begin
      write_shared_key_to_disk
    rescue Exception => e
      error e
    end
  end


  # ----------------------------------------------------------------------
  # Utility methods
  # ----------------------------------------------------------------------

  # Write the shared key to the appropriate file
  def write_shared_key_to_disk
    f = shared_key_file
    note 'Writing shared key to ' + f
    File.open f,'w' do |out|
      out.puts @shared_key
    end
  end

  # Returns the full path for the file for the shared key
  def shared_key_file
    File.join ENV['HOME'],shared_key_file_name
  end

  # Enacpsulates deleting the shared key; return s RET_REMOVED_SHARED_KEY
  def delete_shared_key
    note 'Removing shared key'
    delete_file shared_key_file
    return RET_REMOVED_SHARED_KEY
  end

  # Returns a full url for the page
  def url(page)
    @base_url + page
  end

  # Opens and fully-reads the url
  def open_and_read(url)
    note 'Openning url: ' + url
    open(url).read
  end

  # Saves the song 'info' using our shared key information
  def save_song(info)
    arr = info.map {|k,v| k + '=' + (v ? CGI.escape(v) : '')}
    url = url('post.php') + '?' + arr.join('&')
    puts open_and_read url
  end
  
  # Returns a hash of the song saved last to this user
  def load_resume_info(user)
    url = url('get.php') + '?user=' + CGI.escape(user)
    resume_info = {}
    open_and_read(url).split(/\n/).each do |line|
      parts = line.split /\t/
      resume_info[parts[0]] = parts[1]
    end
    resume_info
  end

  # Returns the track id after loading the song for this user
  def load_song_and_return_track_id(user)
    resume_info = load_resume_info user
    track_id = find_track_id resume_info
    note 'Found track id #' + track_id.to_s
    track_id
  end

  # Returns the key/value strings of this info.  Minimal track info
  # for running and testing
  def track_info_to_string(artist,album,name)
    info = {
      'artist' => artist,
      'track'  => name,
      'album'  => album
    }
    info_to_string info
  end

  # Given the song 'info' returns nicely formatted rows of the info's
  # key/value pairs
  def info_to_string(info)
    res = nil
    space = max_string_length info.keys
    info.each do |k,v|
      res  = res ? res+"\n" : ''
      res += '' * space
      res += ' '
      res += k.to_s + ' : ' + v.to_s
    end
    return res
  end

  # Returns the max length of the strings in arr, or 0, if they are
  # all nil of empty
  def max_string_length(arr)
    max_string = ''
    arr.each do |s|
      max_string = s if not s || s.length > max_string.length
    end
    return max_string.length
  end

  # Prints a message to STDERR if we're in verbose mode
  def debug(s)
    STDERR.puts '>>> ' + s if @verbose
  end

  # ----------------------------------------------------------------------
  # Core implementation
  #----------------------------------------------------------------------

  # This is the method that really initiates everything. It returns
  # one of the RET_ values.
  def run(has_options)

    # We could just want to remove the shared key and return
    # If we go off and try to get one, we may never return
    if has_options.remove_shared_key
      return delete_shared_key
    end

    # Now, make sure we take care of the shared key
    init_shared_key

    ret = nil
    if has_options.just_check
      #
      # Maybe just print the saved song
      #
      note 'Currently-saved song:'
      load_resume_info.each { |k,v| note " - " + k + "\t" + v }
      ret = RET_JUST_CHECKED
    else
      info = read_info @shared_key
      if !has_options.resume && (has_options.pause || info['state'] == PLAYING)
        #
        # Save the current song and pause itunes
        #
        note 'We\'re playing, going to pause'
        if not has_options.motions
          note 'Pausing the player'
          pause_player
          save_song info
        end
        ret = RET_PAUSED
      else
        #
        # Load the saved song and start itunes again
        #
        note 'We\'re not playing, going to start playing'
        if has_options.motions
          track_id = find_track_id info
          note 'Found track #' + track_id.to_s
          note 'Would play this song if not -n'
        else
          track_id = load_song_and_return_track_id @shared_key
          if track_id
            note 'Playing track #' + track_id.to_s
            play_track track_id
          else
            note 'Couldn\'t find this track'
          end
        end
        return RET_PLAYED
      end
    end
    note 'Done'
    ret
  end


  # ----------------------------------------------------------------------
  # Private methods
  # ----------------------------------------------------------------------

  private

  # Given the song 'info' and possibly-nil 'xml', try to find the
  # matching track in our itunes library and return that track's
  # 'track_id'.
  def find_track_id(info)
    t = find_track info
    return t ? t.track_id : nil
  end

  # ----------------------------------------------------------------------
  # Shared key
  #
  # Common is not more abstract then a program running on a computer.
  # That is, we don't have to be more abstract about the way we store
  # the shared key.  It's fine to just store it on the file system and
  # assume we do that.
  # ----------------------------------------------------------------------

  # Finds the shared_key, if we have one; otherwise sets up the server
  # to wait to receive one from the phone
  def init_shared_key

    # First, try to read the shared key from disk.  If it's non-nil
    # we're done.
    return if read_shared_key_from_disk

    # If we don't have a shared key file, start waiting til we get one
    note 'Going to find a shared key'
    find_the_shared_key self
    
  end

  # Reads the shared key from the appropriate file and returns it.  We
  # may have also set it on the command line, so if it's set, don't
  # reset it
  def read_shared_key_from_disk

    # Don' t reset it
    return @shared_key if @shared_key

    # Look in the stored file
    f = shared_key_file
    note 'Reading shared key from ' + f
    if File.exist? f
      note 'The shared key file exists'
      lines = IO.readlines f
      @shared_key = lines[0]
    end
    @shared_key
  end

  # Returns a value based on the class name
  def shared_key_file_name
    '.shared_key-' + self.class.name
  end

end
